﻿#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib2
import lxml.html
import time
import datetime
import tweepy
import ConfigParser
import re
import math
import types
import sys
import webbrowser
import optparse
import locale
import os

# CONSTANTS #

VERSION = '0.3svn1'
CONFIG_FILE = 'last.cfg'
CONFIG_FILE_PATH = os.path.join(os.path.dirname(sys.argv[0]), CONFIG_FILE)
# PLEASE CHANGE THESE KEYS IF YOU'RE MAKING A FORK OF THIS SOURCE
CK = '2aITvaICpP3UMYa8oR7Ebg'
CS = 'QdarPFBmQ0pGsY7kWPK7mdyjf6st90QKImAhhquRh0'
LF = '39b2aeb7a5b0e77933522ebe4aad9d45'


###############################################################################
#                                    INSTALL                                  #
###############################################################################

def install(option, opt, value, parser):
    config = ConfigParser.ConfigParser()
    config.read(CONFIG_FILE_PATH)

    # Twitter init for grabbing keys
    auth = tweepy.OAuthHandler(CK, CS)
    auth_url = auth.get_authorization_url()
    print "Log in to the twitter account where updates are going to be sent"
    print "and authorize: " + auth_url
    browser = raw_input("\nDo you want to open the link in your browser?(y/n)")

    if browser[0] == "y":
        webbrowser.open(auth_url)

    # Grab twitter access keys
    verifier = raw_input('Enter the PIN: ').strip()
    auth.get_access_token(verifier)
    config.set('twitter', 'access_key', auth.access_token.key)
    config.set('twitter', 'access_secret', auth.access_token.secret)

    # Set events id's in the config file
    events = raw_input("\nDo you want to set the last.fm venues ids now?(y/n)")
    if events[0] == "y":
        ids = raw_input("enter the id's separated by commas :")
        config.set('last.fm', 'venues_ids', ids)

    # In case --install is called before the first start of the app
    config.set('DO NOT CHANGE', 'first_run', '0')

    # Mask preview
    mask = config.get('settings', 'mask')
    print "\nMask preview :", mask
    print "Edit last.cfg if you want to change the mask."

    # Save
    config.write(open(CONFIG_FILE_PATH, 'w'))
    print "\nConfiguration done. Run the app without arguments each time\
             you want an update on your twitter account."


###############################################################################
#                                CLASSES & FUNCTIONS                          #
###############################################################################

# CLEANING FUNCTION #

def clean(str):
    return re.sub('\s{2,}', ' ', str)  # Remove extra spaces


# EVENTS LIST HANDLER

def CFG_EVENTS_IDS_init(events):
    if events == "":                # If there's no event
        events = []                 # Create an empty list (avoiding an append
                                    # to an empty first element after a .split)
    else:                           # Otherwise
        events = events.split(',')  # We create a list from the events list
    return events


# URL SHORTENER #

def urlshortener(url):
    short_url = urllib2.urlopen('http://is.gd/api.php?longurl=' + url)
    short_url = short_url.read()
    return short_url


# MASK PARSER #

def maskparser(mp_mask, mp_artist, mp_description, mp_time,
                mp_title, mp_url, mp_website):
    # %time%
    mp_mask = mp_mask.replace('%time%', mp_time)

    # %artist%
    mp_mask = mp_mask.replace('%artist%', mp_artist)

    # %website%
    if "%website%" in mp_mask and mp_website != None:
        short_website = urlshortener(mp_website)
        mp_mask = mp_mask.replace('%website%', short_website)
    else:
        mp_mask = mp_mask.replace('%website%', '')

    # %url%
    if "%url%" in mp_mask and mp_url != None:
        short_url = urlshortener(mp_url)
        mp_mask = mp_mask.replace('%url%', short_url)
    else:
        mp_mask = mp_mask.replace('%url%', '')

    # TITLE & DESCRIPTION SHORTENER #

    mask_len = len(mp_mask)
    if "%title%" in mp_mask:
        title_len = len(mp_title)
        title_tag_len = len("%title%")
        mask_len -= title_tag_len
    else:
        title_len = 0
        title_tag_len = 0

    if "%description%" in mp_mask:
        description_len = len(mp_description)
        description_tag_len = len("%description%")
        mask_len -= description_tag_len
    else:
        description_len = 0
        description_tag_len = 0

    total_len = mask_len + title_len + description_len

    if total_len > 140:
        chars_left = 140 - mask_len  # charleft
        if title_len < chars_left:
            if description_len > 0:
                extra_chars = total_len - 140
                mp_description = mp_description[:-extra_chars - 3]  # -3=='...'
                mp_description = mp_description + '...'
        else:
            if description_len > 0:
                extra_chars = math.trunc(chars_left / 2)
                mp_title = mp_title[:extra_chars]
                mp_title = mp_title + '...'
                chars_left = 140 - len(mp_mask) + len('%description%')
                if description_len > chars_left:
                    mp_description = mp_description[:chars_left - 3]
                    mp_description = mp_description + '...'
            else:
                chars_left = 140 - len(mp_mask) + title_tag_len\
                                            + description_tag_len
                mp_title = mp_title[:chars_left - 3]
                mp_title = mp_title + '...'

    mp_mask = mp_mask.replace('%title%', mp_title)
    mp_mask = mp_mask.replace('%description%', mp_description)

    return clean(mp_mask)


# FEED PARSER #

class FeedParser:
    def __init__(self, event):
        self.event = event
        # Make the strptime operation work otherwise an error is thrown
        locale.setlocale(locale.LC_TIME, 'C')

    def remove_html_tags(self, content):
        content = lxml.html.fromstring(content)
        content = content.xpath("//div/descendant::text()")
        content = ' '.join(content)
        return content

    def getid(self):
        event_id = self.event.xpath('id')
        event_id = event_id[0].text
        return event_id

    def getdate(self):
        # Raw last.fm date
        startDate = self.event.xpath('startDate')
        raw_date = startDate[0].text
        # Struct time
        struct_date = time.strptime(raw_date, "%a, %d %b %Y %H:%M:%S")
        # Configfile time format
        parsed_date = datetime.datetime(*struct_date[0:5])
        parsed_date = parsed_date.date()
        return raw_date, struct_date, parsed_date

    def getartist(self):
        artists = self.event.xpath('artists/artist')
        artist_list = []
        for artist in artists:
            artist_list.append(artist.text)
        artist_list = ', '.join(artist_list)
        return artist_list

    def gettitle(self):
        title = self.event.xpath('title')
        title = title[0].text
        if type(title) is types.NoneType:
            title = ''
        title = clean(title)
        return title

    def getdescription(self):
        description = self.event.xpath('description')
        description = description[0].text
        if type(description) is types.NoneType:
            description = ''
        else:
            description = self.remove_html_tags(description)
            description = clean(description)
        return description

    def geturl(self):
        url = self.event.xpath('url')
        url = url[0].text
        return url

    def getwebsite(self):
        website = self.event.xpath('website')
        website = website[0].text
        return website


###############################################################################
#                                    MAIN                                     #
###############################################################################

def main():
    # INIT STUFF #

    now = datetime.datetime(*time.localtime()[0:5])
    NOW_DATE = now.date()
    new_events = 0

    # CONFIG FILE #

    config = ConfigParser.ConfigParser()
    config.read(CONFIG_FILE_PATH)

    mask = config.get('settings', 'mask')

    ACCESS_KEY = config.get('twitter', 'access_key')
    ACCESS_SECRET = config.get('twitter', 'access_secret')

    CFG_LASTDATE = config.get('DO NOT CHANGE', 'lastdate')
    if str(NOW_DATE) != CFG_LASTDATE:
        config.set('DO NOT CHANGE', 'events_ids', '')

    CFG_VENUES_IDS = config.get('last.fm', 'venues_ids')
    CFG_VENUES_IDS = CFG_VENUES_IDS.split(',')
    CFG_EVENTS_IDS = config.get('DO NOT CHANGE', 'events_ids')
    CFG_EVENTS_IDS = CFG_EVENTS_IDS_init(CFG_EVENTS_IDS)

    if CFG_VENUES_IDS[0] == '':
        print "No events ids are specified in the config file,\
                edit the last.cfg file and restart the application"
        sys.exit(1)
    elif ACCESS_KEY == '' or ACCESS_SECRET == '':
        print "Twitter keys are missing, please restart the\
                application with --install argument or fill the last.cfg file"
        sys.exit(1)

    # TWITTER AUTHENTICATION #

    auth = tweepy.OAuthHandler(CK, CS)
    auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
    api = tweepy.API(auth)

    # PARSE & SUBMIT #

    for venue in CFG_VENUES_IDS:  # FOR EACH VENUE IN THE CONFIG FILE...
        venue = venue.replace(' ', '')  # Removes extra spaces
        page = urllib2.urlopen('http://ws.audioscrobbler.com/2.0/\
                ?method=venue.getevents&api_key=' + LF + '&venue=' + venue)
        doc = lxml.etree.parse(page)
        events = doc.xpath('//event')

        for event in events:  # FOR EACH EVENT IN THE CONFIG FILE...
            feed = FeedParser(event)  # Parse the last.fm feed

            # Grab needed datas from the feed
            event_raw_date, event_struct_date,\
                            event_parsed_date = feed.getdate()
            # if the event is too far in the future we stop parsing the feed
            if event_parsed_date > NOW_DATE: break
            artist = feed.getartist()
            title = feed.gettitle()
            description = feed.getdescription()
            url = feed.geturl()
            website = feed.getwebsite()
            event_id = feed.getid()

            # IF THE EVENT FROM THE FEED HAS NOT ALREADY BEEN PARSED...
            if not event_id in CFG_EVENTS_IDS:
                # CHECK IF TODAY IS THE EVENT DATE, OTHERWISE WE DON'T CARE
                if NOW_DATE == event_parsed_date:
                    # Time format used for twitter
                    mask_time = time.strftime('%H:%M', event_struct_date)
                    # Build our twitter message, parsing and making it fit
                    # in 140 character
                    content = maskparser(mask, artist, description, mask_time,
                                        title, url, website)

                    # Sends content to twitter
                    api.update_status(content)

                    # Let know in the event list that
                    # this event has been processed
                    CFG_EVENTS_IDS.append(event_id)
                    new_events += 1

    # EVENTS NOTIFIER
    # if new_events == 0: print "No new events"

    # SAVING CONFIG
    CFG_EVENTS_IDS = ','.join(CFG_EVENTS_IDS)
    config.set('DO NOT CHANGE', 'events_ids', CFG_EVENTS_IDS)
    config.set('DO NOT CHANGE', 'lastdate', NOW_DATE)
    config.write(open(CONFIG_FILE_PATH, 'w'))


###############################################################################
#                                OPTPARSE                                     #
###############################################################################

usage = "usage: %prog [options]\n Specifying no options will start the \
        application if --install(ed)"
parser = optparse.OptionParser(usage=usage, version="%prog " + VERSION)
parser.add_option("-i", "--install",
                  action="callback", callback=install,
                  help="install the application")

(options, args) = parser.parse_args()

if len(args) == 0:
    config = ConfigParser.ConfigParser()
    config.read(CONFIG_FILE_PATH)
    firstrun = int(config.get('DO NOT CHANGE', "first_run"))
    if firstrun:
        install("du", "m", "m", "y")
    else:
        main()
